package com.chainable.nftserver.core.asset.service.impl;

import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chainable.nftserver.admin.shop.dto.enums.ChargingModelEnum;
import com.chainable.nftserver.common.api.ApiErrorCode;
import com.chainable.nftserver.common.api.OpenAPIResp;
import com.chainable.nftserver.common.api.OpenAPIRespBuilder;
import com.chainable.nftserver.common.enums.TxStatusEnum;
import com.chainable.nftserver.common.enums.TxTypeEnum;
import com.chainable.nftserver.common.utils.Utils;
import com.chainable.nftserver.core.account.entity.ChainAccountDO;
import com.chainable.nftserver.core.account.service.ChainAccountService;
import com.chainable.nftserver.core.asset.dao.AssetShareMapper;
import com.chainable.nftserver.core.asset.entity.*;
import com.chainable.nftserver.core.asset.service.*;
import com.chainable.nftserver.core.gas.dto.GasFlowDTO;
import com.chainable.nftserver.core.gas.dto.enums.GasFlowTypeEnum;
import com.chainable.nftserver.core.gas.service.GasFlowService;
import com.chainable.nftserver.core.shop.entity.ShopCallInterfaceRecordDO;
import com.chainable.nftserver.core.shop.entity.ShopChargingDO;
import com.chainable.nftserver.core.shop.entity.ShopDO;
import com.chainable.nftserver.core.shop.service.ShopChargingService;
import com.chainable.nftserver.core.shop.service.ShopService;
import com.chainable.nftserver.core.txrecord.repo.TransactionRecordRepo;
import com.chainable.nftserver.core.web3j.service.NFTTokenService;
import com.chainable.nftserver.openapi.account.dto.enums.AccountStatusEnum;
import com.chainable.nftserver.openapi.activity.dto.request.ActivityDetailInfo;
import com.chainable.nftserver.openapi.activity.dto.request.ActivityUpsertRequest;
import com.chainable.nftserver.openapi.asset.dto.enums.AssetShareStatusEnum;
import com.chainable.nftserver.openapi.asset.dto.request.ShareDTO;
import com.chainable.nftserver.openapi.asset.dto.request.ShareGrantRequest;
import com.chainable.nftserver.openapi.asset.dto.request.ShareQueryRequest;
import com.chainable.nftserver.openapi.asset.dto.response.*;
import lombok.extern.slf4j.Slf4j;
import org.chainmaker.sdk.model.BlockInfo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.web3j.protocol.core.methods.response.TransactionReceipt;

import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * @Author wang jian
 * @Date 2025/2/26 15:29
 */
@Service
@Slf4j
public class AssetShareServiceImpl extends ServiceImpl<AssetShareMapper, AssetShareDO> implements AssetShareService {

    @Autowired
    private ShopService shopService;

    @Autowired
    private AssetService assetService;

    @Autowired
    private ChainAccountService accountService;

    @Autowired
    private AssetShareTransactionService transactionService;

    @Autowired
    private NFTTokenService nftService;

    @Autowired
    private ShopChargingService chargingService;

    @Autowired
    private GasFlowService gasFlowService;

    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private DataSourceTransactionManager transactionManager;

    @Autowired
    private TransactionDefinition transactionDefinition;
    @Resource
    private AssetMintTaskService assetMintTaskService;
    @Resource
    private AssetTransferTaskService assetTransferTaskService;

    @Resource
    private TransactionRecordRepo transactionRecordRepo;

    // 注入自身
    @Lazy
    @Resource
    private AssetShareServiceImpl assetShareService;

    private OpenAPIResp checkRequestIdIsRepetitive(String requestId, Integer shopId) {
        AssetShareTransactionDO existShareTransaction = transactionService.findByRequestIdAndShopId(
                requestId, shopId);
        if (null != existShareTransaction) {
            log.warn("repetitive request id:{},shop id:{}", requestId, shopId);
            return OpenAPIRespBuilder.success(existShareTransaction.getOperationId());
        }
        return null;
    }

    @Override
    public OpenAPIResp<BaseResp> grantShare(ShareGrantRequest grantRequest, TxTypeEnum txTypeEnum) {
        ShopDO shopDO = shopService.getShopByAppId(grantRequest.getAppId());
        ChainAccountDO chainAccountDO = accountService.findByShopIdAndAddress(shopDO.getId(),
                grantRequest.getToAccount());
        if (null == chainAccountDO) {
            log.error("not exist chain account:{},shop id:{}", grantRequest.getToAccount(), shopDO.getId());
            return OpenAPIRespBuilder.failure(ApiErrorCode.NOT_EXIST_ACCOUNT);
        }
        if (chainAccountDO.getAccountStatus().equals(AccountStatusEnum.FROZEN.getStatus())) {
            log.error("chain account had frozen,account id:{}", chainAccountDO.getId());
            return OpenAPIRespBuilder.failure(ApiErrorCode.ACCOUNT_FROZEN);
        }
        OpenAPIResp resp = checkRequestIdIsRepetitive(grantRequest.getRequestId(), shopDO.getId());
        if (null != resp) {
            return resp;
        }
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
        AssetDO assetDO;
        if (null != grantRequest.getAssetId()) {
            assetDO = assetService.findByShopIdAndAssetIdForUpdate(shopDO.getId(), grantRequest.getAssetId());
        } else {
            assetDO = assetService.findByShopIdAndContractAddressForUpdate(shopDO.getId(),
                    grantRequest.getContractAddress());
        }
        if (null == assetDO) {
            log.error("not exist asset id:{},contract address:{},shop id:{}", grantRequest.getAssetId(),
                    grantRequest.getContractAddress(),
                    shopDO.getId());
            transactionManager.commit(transactionStatus);
            return OpenAPIRespBuilder.failure(ApiErrorCode.NOT_EXIST_ASSET_ID);
        }
        if (!assetDO.getStatus().equals(TxStatusEnum.SUCCEED.getStatus())) {
            log.error("asset status not support grant share,asset id:{}", assetDO.getId());
            transactionManager.commit(transactionStatus);
            return OpenAPIRespBuilder.failure(ApiErrorCode.NOT_SUPPORT_GRANT_SHARE);
        }
        AssetShareDO assetShareDO = findByAssetIdAndShareId(assetDO.getId(), grantRequest.getShareId());
        if (null != assetShareDO) {
            log.error("exist asset share, id:{},share id:{}", assetDO.getId(), grantRequest.getShareId());
            transactionManager.commit(transactionStatus);
            return OpenAPIRespBuilder.failure(ApiErrorCode.GRANTED_SHARE_ID);
        }

        if (!assetDO.getAmount().equals(AssetService.UNLIMITED)) {
            if (assetDO.getSurplusAmount() <= AssetService.UNLIMITED) {
                log.error("beyond mint amount,asset id:{}", assetDO.getId());
                transactionManager.commit(transactionStatus);
                return OpenAPIRespBuilder.failure(ApiErrorCode.BEYOND_MAX_GRANT_AMOUNT);
            }
            assetService.subtractSurplusAmount(assetDO.getId());
        }
        //检查调用次数是否足够,并将调用记录进行保存
        ShopChargingDO shopChargingDO = chargingService.findByShopIdForUpdate(shopDO.getId());
        if (shopChargingDO.getChargingModel().equals(ChargingModelEnum.GAS.getType())) {
            int residualTimes = shopChargingDO.getUsableTimes() - shopChargingDO.getFrozenTimes();
            if (residualTimes <= 0) {
                log.warn("no residual times to use,shop id:{}", shopDO.getId());
                transactionManager.rollback(transactionStatus);
                return OpenAPIRespBuilder.failure(ApiErrorCode.NO_TIME_TO_USE);
            }
            chargingService.addFrozenTimes(shopChargingDO.getId());
        }
        assetShareDO = AssetShareDO.builder().assetId(assetDO.getId()).shareId(grantRequest.getShareId())
                .account(grantRequest.getToAccount()).shopId(shopDO.getId()).shareUri(grantRequest.getShareURI())
                .shareStatus(AssetShareStatusEnum.INIT.getShareStatus()).status(TxStatusEnum.PENDING.getStatus()).build();
        String operationId = Utils.getRandomId();
        try {
            this.baseMapper.insert(assetShareDO);
        } catch (Exception e) {
            log.error("grant share failed,message:{}", e.getMessage());
            transactionManager.rollback(transactionStatus);
            return OpenAPIRespBuilder.failure(ApiErrorCode.NET_ERROR);
        }
        AssetShareTransactionDO assetShareTransactionDO = constructShareTransaction(assetDO, assetShareDO,
                grantRequest, operationId, txTypeEnum);
        try {
            transactionService.saveOrUpdateTransaction(assetShareTransactionDO);
            // 保存到异步任务表
            AssetMintTaskDO assetMintTaskDO = new AssetMintTaskDO();
            assetMintTaskDO.setTxId(assetShareTransactionDO.getId());
            assetMintTaskService.save(assetMintTaskDO);
            //手动提交事务，避免链异步通知已到，本地事务 还没有提交
            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
            log.error("save transaction error,message:{}", e.getMessage());
            transactionManager.rollback(transactionStatus);
            return OpenAPIRespBuilder.failure(ApiErrorCode.NET_ERROR);
        }
        BaseResp baseResp = new BaseResp();
        baseResp.setOperationId(operationId);
        return OpenAPIRespBuilder.success(baseResp);
    }

    private ShopCallInterfaceRecordDO constructInterfaceRecord(Integer shopId, String appId, Integer usedTimes,
                                                               int txType) {
        ShopCallInterfaceRecordDO recordDO = new ShopCallInterfaceRecordDO();
        recordDO.setShopId(shopId);
        recordDO.setAppId(appId);
        recordDO.setUsedTimes(usedTimes);
        recordDO.setStatus(TxStatusEnum.SUCCEED.getStatus());
        recordDO.setInterfaceType(txType);
        return recordDO;
    }

    @Override
    public AssetShareDO findByIdForUpdate(Integer id) {
        return this.baseMapper.findByIdForUpdate(id);
    }

    private AssetShareTransactionDO constructShareTransaction(AssetDO assetDO, AssetShareDO assetShareDO,
                                                              ShareGrantRequest request, String operationId, TxTypeEnum txTypeEnum) {
        AssetShareTransactionDO shareTransactionDO = AssetShareTransactionDO.builder()
                .assetShareId(assetShareDO.getId()).fromAccount(request.getFromAccount())
                .txType(txTypeEnum.getTxType())
                .assetId(assetDO.getId().longValue()).contractAddress(assetDO.getContractAddress()).activeId(request.getActiveId())
                .shareId(assetShareDO.getShareId()).shopId(assetDO.getShopId()).toAccount(request.getToAccount())
                .status(TxStatusEnum.PENDING.getStatus()).requestId(request.getRequestId()).operationId(operationId)
                .build();
        return shareTransactionDO;
    }

    @Override
    public AssetShareDO findByAssetIdAndShareId(Integer assetId, Long shareId) {
        LambdaQueryWrapper<AssetShareDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AssetShareDO::getAssetId, assetId);
        queryWrapper.eq(AssetShareDO::getShareId, shareId);
        return this.baseMapper.selectOne(queryWrapper);
    }

    @Override
    public AssetShareDO findByAssetIdAndShareIdForUpdate(Integer assetId, Long shareId) {
        return this.baseMapper.findByAssetIdAndShareIdForUpdate(assetId, shareId);
    }

    @Override
    public OpenAPIResp asyncTransferShare(ShareGrantRequest grantRequest, TxTypeEnum txTypeEnum) {
        ShopDO shopDO = shopService.getShopByAppId(grantRequest.getAppId());
        ChainAccountDO chainAccountDO = accountService.findByShopIdAndAddress(shopDO.getId(),
                grantRequest.getToAccount());
        if (null == chainAccountDO) {
            log.warn("not exist to chain account:{},shop id:{}", grantRequest.getToAccount(), shopDO.getId());
            return OpenAPIRespBuilder.failure(ApiErrorCode.NOT_EXIST_ACCOUNT);
        }
        if (chainAccountDO.getAccountStatus().equals(AccountStatusEnum.FROZEN.getStatus())) {
            log.warn("chain account had frozen,account id:{}", chainAccountDO.getId());
            return OpenAPIRespBuilder.failure(ApiErrorCode.ACCOUNT_FROZEN);
        }
        ChainAccountDO fromChainAccount = accountService.findByShopIdAndAddress(shopDO.getId(),
                grantRequest.getFromAccount());

        // 如果传递了原始数据与签名，则根据用户的sm2公钥，对原始数据进行验证签名
        if (StrUtil.isNotBlank(grantRequest.getMessage()) && StrUtil.isNotBlank(grantRequest.getSignature())) {
            SM2 sm2 = SmUtil.sm2(null, fromChainAccount.getDidPubkey());
            byte[] signature = HexUtil.decodeHex(grantRequest.getSignature());
            boolean verify = sm2.verify(grantRequest.getMessage().getBytes(), signature);
            if (!verify) {
                return OpenAPIRespBuilder.failure(ApiErrorCode.TRANSFER_NFT_SIGNATURE_INVALID);
            }
        }

        if (null == fromChainAccount) {
            log.warn("not exist from chain account:{},shop id:{}", grantRequest.getToAccount(), shopDO.getId());
            return OpenAPIRespBuilder.failure(ApiErrorCode.NOT_EXIST_ACCOUNT);
        }
        if (fromChainAccount.getAccountStatus().equals(AccountStatusEnum.FROZEN.getStatus())) {
            log.warn("chain account had frozen,account id:{}", fromChainAccount.getId());
            return OpenAPIRespBuilder.failure(ApiErrorCode.ACCOUNT_FROZEN);
        }
        OpenAPIResp resp = checkRequestIdIsRepetitive(grantRequest.getRequestId(), shopDO.getId());
        if (null != resp) {
            return resp;
        }
        AssetDO assetDO = null;
        if (null != grantRequest.getAssetId()) {
            assetDO = assetService.findByShopIdAndAssetId(shopDO.getId(), grantRequest.getAssetId());
        } else {
            assetDO = assetService.findByShopIdAndContractAddress(shopDO.getId(),
                    grantRequest.getContractAddress());
        }
        if (null == assetDO) {
            log.warn("not exist asset,asset id:{} or contact address:{}", grantRequest.getAssetId(),
                    grantRequest.getContractAddress());
            return OpenAPIRespBuilder.failure(ApiErrorCode.NOT_EXIST_ASSET_ID);
        }
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
        AssetShareDO assetShareDO = findByAssetIdAndShareIdForUpdate(assetDO.getId(), grantRequest.getShareId());
        if (null == assetShareDO) {
            log.warn("not exist asset share:{},id:{}", grantRequest.getShareId(), assetDO.getId());
            transactionManager.commit(transactionStatus);
            return OpenAPIRespBuilder.failure(ApiErrorCode.NOT_EXIST_SHARE_ID);
        }
        if (!assetShareDO.getStatus().equals(TxStatusEnum.SUCCEED.getStatus())) {
            log.warn("asset share status not support to tx,share id:{},status:{}", assetShareDO.getId(),
                    assetShareDO.getStatus());
            transactionManager.commit(transactionStatus);
            return OpenAPIRespBuilder.failure(ApiErrorCode.SHARE_NOT_TRANSFER);
        }
        if (!assetShareDO.getAccount().equals(fromChainAccount.getAddress())) {
            log.warn("not share owner,owner account:{},account:{}", assetShareDO.getAccount(),
                    fromChainAccount.getAddress());
            transactionManager.commit(transactionStatus);
            return OpenAPIRespBuilder.failure(ApiErrorCode.NOT_SHARE_OWNER);
        }
        if (assetShareDO.getShareStatus().equals(AssetShareStatusEnum.FROZEN.getShareStatus())) {
            log.warn("share status is frozen:{}", assetShareDO.getId());
            transactionManager.commit(transactionStatus);
            return OpenAPIRespBuilder.failure(ApiErrorCode.SHARE_NOT_TRANSFER);
        }
        //检查gas是否足够,并将调用记录进行保存
        ShopChargingDO shopChargingDO = chargingService.findByShopIdForUpdate(shopDO.getId());
        if (shopChargingDO.getChargingModel().equals(ChargingModelEnum.GAS.getType())) {
            int residualTimes = shopChargingDO.getUsableTimes() - shopChargingDO.getFrozenTimes();
            if (residualTimes <= 0) {
                log.warn("no residual times to use,shop id:{}", shopDO.getId());
                transactionManager.rollback(transactionStatus);
                return OpenAPIRespBuilder.failure(ApiErrorCode.NO_TIME_TO_USE);
            }
            chargingService.addFrozenTimes(shopChargingDO.getId());
        }
        assetShareDO.setShareStatus(AssetShareStatusEnum.FROZEN.getShareStatus());
        this.baseMapper.updateById(assetShareDO);
        String operationId = Utils.getRandomId();
        AssetShareTransactionDO assetShareTransactionDO = constructShareTransaction(assetDO, assetShareDO,
                grantRequest, operationId, txTypeEnum);
        try {
            transactionService.saveOrUpdateTransaction(assetShareTransactionDO);
            // 保存到异步任务表
            AssetTransferTaskDO assetTransferTaskDO = new AssetTransferTaskDO();
            assetTransferTaskDO.setTxId(assetShareTransactionDO.getId());
            assetTransferTaskDO.setFromAccountId(fromChainAccount.getId());
            assetTransferTaskService.save(assetTransferTaskDO);
            //手动提交事务，避免链异步通知已到，本地事务 还没有提交
            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
            log.error("save transaction,error:{}", e.getMessage());
            transactionManager.rollback(transactionStatus);
            return OpenAPIRespBuilder.failure(ApiErrorCode.NET_ERROR);
        }

        BaseResp baseResp = new BaseResp();
        baseResp.setOperationId(operationId);
        return OpenAPIRespBuilder.success(baseResp);
    }

    @Override
    public OpenAPIResp getAssetShareDetail(ShareQueryRequest queryRequest) {
        ShopDO shopDO = shopService.getShopByAppId(queryRequest.getAppId());
        AssetDO assetDO;
        if (null != queryRequest.getAssetId()) {
            assetDO = assetService.findByShopIdAndAssetId(shopDO.getId(), queryRequest.getAssetId());
        } else {
            assetDO = assetService.findByShopIdAndContractAddress(shopDO.getId(),
                    queryRequest.getContractAddress());
        }
        if (null == assetDO) {
            return OpenAPIRespBuilder.failure(ApiErrorCode.NOT_EXIST_ASSET_ID);
        }
        AssetShareDO shareDO = findByAssetIdAndShareId(assetDO.getId(), queryRequest.getShareId());
        if (null == shareDO) {
            return OpenAPIRespBuilder.failure(ApiErrorCode.NOT_EXIST_SHARE_ID);
        }
        AssetShareDetailMeta assetShareDetailMeta = new AssetShareDetailMeta();
        BeanUtils.copyProperties(shareDO, assetShareDetailMeta);
        assetShareDetailMeta.setShareURI(shareDO.getShareUri());
        AssetMeta assetMeta = new AssetMeta();
        BeanUtils.copyProperties(assetDO, assetMeta);
        assetShareDetailMeta.setAssetMeta(assetMeta);
        return OpenAPIRespBuilder.success(assetShareDetailMeta);
    }

    @Override
    public OpenAPIResp pageAssetShare(ShareQueryRequest queryRequest) {
        Page<Object> page = new Page<>(queryRequest.getPageNo(), queryRequest.getPageSize());
        ShopDO shopDO = shopService.getShopByAppId(queryRequest.getAppId());
        AssetDO assetDO;
        if (null != queryRequest.getAssetId()) {
            assetDO = assetService.findByShopIdAndAssetId(shopDO.getId(), queryRequest.getAssetId());
        } else {
            assetDO = assetService.findByShopIdAndContractAddress(shopDO.getId(),
                    queryRequest.getContractAddress());
        }
        if (null == assetDO) {
            return OpenAPIRespBuilder.failure(ApiErrorCode.NOT_EXIST_ASSET_ID);
        }
        Page<AssetShareMeta> pageResult = this.baseMapper.pageAssetShare(page, shopDO.getId(), assetDO.getId());
        return OpenAPIRespBuilder.success(pageResult);
    }

    @Override
    public OpenAPIResp pageAssetShareByAcc(ShareQueryRequest queryRequest) {
        Page<Object> page = new Page<>(queryRequest.getPageNo(), queryRequest.getPageSize());
        ShopDO shopDO = shopService.getShopByAppId(queryRequest.getAppId());
        Page<AssetShareInfo> pageResult = this.baseMapper.pageAssetShareByAcc(page, shopDO.getId(),
                queryRequest);
        return OpenAPIRespBuilder.success(pageResult);
    }

    @Override
    public void mintShare(AssetDO assetDO, ShareDTO shareDTO) {
        AssetShareDO assetShareDO = findByAssetIdAndShareId(shareDTO.getAssetPrimaryKey(),
                shareDTO.getShareId());
        //手动提交事务，避免for循环修复时铸币锁的时间太长
        AssetShareTransactionDO assetShareTransactionDO = transactionService.findByIdForUpdate(
                shareDTO.getTransactionId());
        log.debug("=============开始铸币:{}", shareDTO.getShareId());
        TransactionReceipt transactionReceipt = nftService.mint(shareDTO.getShareId(),
                shareDTO.getToAccount(),
                shareDTO.getContractAddress(), assetShareDO.getShareUri());
        String txHash = transactionReceipt.getTransactionHash();

        // 保存交易记录
        transactionRecordRepo.saveTxRecord(assetDO.getShopId(), transactionReceipt);

        log.debug("============ mint txHash:{}", txHash);
        assetShareTransactionDO.setTxHash(txHash);
        mintPending(assetShareDO, txHash, assetShareTransactionDO);
    }

    @Override
    public void mintFailed(AssetShareDO assetShareDO, AssetShareTransactionDO assetShareTransactionDO) {
        assetShareDO.setStatus(TxStatusEnum.FAILED.getStatus());
        assetShareTransactionDO.setStatus(TxStatusEnum.FAILED.getStatus());
        //手动提交事务，避免for循环修复时铸币锁的时间太长
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
        try {
            this.baseMapper.updateById(assetShareDO);
            transactionService.saveOrUpdateTransaction(assetShareTransactionDO);
            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
            log.error("update asset share status failed when fix share message:{}", e);
            transactionManager.rollback(transactionStatus);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void mintSuccess(AssetShareDO assetShareDO, AssetShareTransactionDO assetShareTransactionDO, String txHash,
                            Long blockHeight, Long blockTime) {
        //手动提交事务，避免for循环修复时铸币锁的时间太长
        AssetShareTransactionDO forUpdateTransaction = transactionService.findByIdForUpdate(
                assetShareTransactionDO.getId());
        if (!forUpdateTransaction.getStatus().equals(TxStatusEnum.PENDING.getStatus())) {
            return;
        }
        ShopChargingDO shopChargingDO = chargingService.findByShopIdForUpdate(assetShareDO.getShopId());
        ShopDO shop = shopService.getShopById(assetShareDO.getShopId());
        GasFlowDTO gasFlowDTO = null;
        if (shopChargingDO.getChargingModel().equals(ChargingModelEnum.GAS.getType())) {
            shopChargingDO.setFrozenTimes(shopChargingDO.getFrozenTimes() - 1);
            shopChargingDO.setUsedTimes(shopChargingDO.getUsedTimes() + 1);
            shopChargingDO.setUsableTimes(shopChargingDO.getUsableTimes() - 1);
            gasFlowDTO = GasFlowDTO.builder().amount(-1).usedGas(shopChargingDO.getUsedTimes())
                    .shopId(shopChargingDO.getShopId()).workbenchUserId(shop.getWorkbenchUserId())
                    .usableGas(shopChargingDO.getUsableTimes()).shopId(shopChargingDO.getShopId()).build();
            setGasFlowType(gasFlowDTO, assetShareTransactionDO.getTxType());
        } else {
            shopChargingDO.setUsedTimes(shopChargingDO.getUsedTimes() + 1);
        }
        ShopCallInterfaceRecordDO callInterfaceRecordDO = constructInterfaceRecord(shop.getId(),
                shop.getAppId(), shopChargingDO.getUsedTimes(),
                assetShareTransactionDO.getTxType());
        try {
            assetShareDO.setStatus(TxStatusEnum.SUCCEED.getStatus());
            assetShareDO.setBlockTime(blockTime);
            assetShareDO.setBlockHeight(blockHeight);
            assetShareDO.setTxHash(txHash);
            assetShareTransactionDO.setStatus(TxStatusEnum.SUCCEED.getStatus());
            assetShareTransactionDO.setBlockHeight(blockHeight);
            assetShareTransactionDO.setBlockTime(blockTime);
            assetShareTransactionDO.setTxHash(txHash);
            chargingService.insertOrUpdateShopCallInterfaceRecord(callInterfaceRecordDO);
            chargingService.saveOrUpdateShopCharging(shopChargingDO);
            this.baseMapper.updateById(assetShareDO);
            transactionService.saveOrUpdateTransaction(assetShareTransactionDO);
            if (null != gasFlowDTO) {
                gasFlowService.insertGasFlow(gasFlowDTO);
            }
            // 成功后删除任务表
            assetMintTaskService.removeById(assetShareTransactionDO.getId());
        } catch (Exception e) {
            log.error("mint share failed message:{}", e);
        }
    }

    private void setGasFlowType(GasFlowDTO gasFlowDTO, int txType) {
        switch (txType) {
            case 1:
                gasFlowDTO.setType(GasFlowTypeEnum.Grant_Share.getType());
                break;
            case 2:
                gasFlowDTO.setType(GasFlowTypeEnum.Transfer_Share.getType());
                break;
            case 3:
                break;
            case 4:
                gasFlowDTO.setType(GasFlowTypeEnum.Collect_Share.getType());
                break;
            case 5:
                gasFlowDTO.setType(GasFlowTypeEnum.Combination_Share.getType());
                break;
            default:
                break;
        }
    }

    @Override
    public void mintPending(AssetShareDO assetShareDO, String txHash, AssetShareTransactionDO assetShareTransactionDO) {
        assetShareDO.setStatus(TxStatusEnum.PENDING.getStatus());
        assetShareDO.setTxHash(txHash);
        this.baseMapper.updateById(assetShareDO);
        assetShareTransactionDO.setStatus(TxStatusEnum.PENDING.getStatus());
        assetShareTransactionDO.setTxHash(txHash);
        transactionService.saveOrUpdateTransaction(assetShareTransactionDO);
    }

    @Override
    public void transferShare(AssetDO assetDO, ShareDTO shareDTO) {
        log.info("transfer share start, shareDTO:{}", shareDTO);
        AssetShareDO assetShareDO = findByAssetIdAndShareId(shareDTO.getAssetPrimaryKey(),
                shareDTO.getShareId());
        if (null != shareDTO.getIsRecycle() && shareDTO.getIsRecycle()) {
            assetShareDO.setShareStatus(AssetShareStatusEnum.RECYCLE.getShareStatus());
            this.baseMapper.updateById(assetShareDO);
        }
        AssetShareTransactionDO assetShareTransactionDO = transactionService.findByIdForUpdate(
                shareDTO.getTransactionId());
        log.info("transfer share start, assetShareTransactionDO:{}", assetShareTransactionDO);
        String txHash = null;
        try {
            TransactionReceipt txRecpt = nftService.transfer(shareDTO.getFromAccount(),
                    shareDTO.getToAccount(),
                    shareDTO.getShareId(), shareDTO.getContractAddress());
            txHash = txRecpt.getTransactionHash();
            log.info("transfer share txHash:{}", txHash);

            // 保存交易记录
            transactionRecordRepo.saveTxRecord(assetDO.getShopId(), txRecpt);
        } catch (Exception e) {
            log.error("transfer share failed message", e);
        }
        if (null != txHash) {
            transferPending(txHash, assetShareTransactionDO);
        } else {
            transferFailed(assetShareDO, assetShareTransactionDO, null);
        }
    }

    @Override
    public void transferSuccess(AssetShareDO assetShareDO,
                                AssetShareTransactionDO assetShareTransactionDO, String txHash, Long blockHeight, Long blockTime) {
        assetShareDO.setAccount(assetShareTransactionDO.getToAccount());
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
        try {
            if (!assetShareTransactionDO.getTxType().equals(TxTypeEnum.RECYCLE_SHARE.getTxType())) {
                assetShareDO.setShareStatus(AssetShareStatusEnum.INIT.getShareStatus());
                ShopDO shop = shopService.getShopById(assetShareDO.getShopId());
                ShopChargingDO shopChargingDO = chargingService.findByShopIdForUpdate(assetShareDO.getShopId());
                if (shopChargingDO.getChargingModel().equals(ChargingModelEnum.GAS.getType())) {
                    shopChargingDO.setFrozenTimes(shopChargingDO.getFrozenTimes() - 1);
                    shopChargingDO.setUsableTimes(shopChargingDO.getUsableTimes() - 1);
                    shopChargingDO.setUsedTimes(shopChargingDO.getUsedTimes() + 1);
                    GasFlowDTO gasFlowDTO = GasFlowDTO.builder().amount(-1).usedGas(shopChargingDO.getUsedTimes())
                            .usableGas(shopChargingDO.getUsableTimes()).workbenchUserId(shop.getWorkbenchUserId())
                            .shopId(shopChargingDO.getShopId()).build();
                    setGasFlowType(gasFlowDTO, assetShareTransactionDO.getTxType());
                    gasFlowService.insertGasFlow(gasFlowDTO);
                } else {
                    shopChargingDO.setUsedTimes(shopChargingDO.getUsedTimes() + 1);
                }
                ShopCallInterfaceRecordDO callInterfaceRecordDO = constructInterfaceRecord(shop.getId(),
                        shop.getAppId(), shopChargingDO.getUsedTimes(),
                        assetShareTransactionDO.getTxType());
                chargingService.insertOrUpdateShopCallInterfaceRecord(callInterfaceRecordDO);
                chargingService.saveOrUpdateShopCharging(shopChargingDO);
            } else {
                assetShareDO.setShareStatus(AssetShareStatusEnum.RECYCLE.getShareStatus());
            }
            this.baseMapper.updateById(assetShareDO);
            assetShareTransactionDO.setStatus(TxStatusEnum.SUCCEED.getStatus());
            assetShareTransactionDO.setBlockHeight(blockHeight);
            assetShareTransactionDO.setTxHash(txHash);
            assetShareTransactionDO.setBlockTime(blockTime);
            transactionService.saveOrUpdateTransaction(assetShareTransactionDO);
            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
            transactionManager.rollback(transactionStatus);
        }
    }

    @Override
    public void transferFailed(AssetShareDO assetShareDO,
                               AssetShareTransactionDO assetShareTransactionDO, String txHash) {
        assetShareTransactionDO.setStatus(TxStatusEnum.FAILED.getStatus());
        assetShareTransactionDO.setTxHash(txHash);
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
        try {
            if (!assetShareTransactionDO.getTxType().equals(TxTypeEnum.RECYCLE_SHARE.getTxType())) {
                ShopChargingDO shopChargingDO = chargingService.findByShopIdForUpdate(assetShareDO.getShopId());
                if (shopChargingDO.getChargingModel().equals(ChargingModelEnum.GAS.getType())) {
                    chargingService.subtractFrozenTimes(shopChargingDO.getId());
                }
            } else {
                assetShareDO.setShareStatus(AssetShareStatusEnum.INIT.getShareStatus());
                this.baseMapper.updateById(assetShareDO);
            }
            transactionService.saveOrUpdateTransaction(assetShareTransactionDO);
            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
            transactionManager.rollback(transactionStatus);
        }
    }

    @Override
    public void transferPending(String txHash, AssetShareTransactionDO assetShareTransactionDO) {
        assetShareTransactionDO.setStatus(TxStatusEnum.PENDING.getStatus());
        assetShareTransactionDO.setTxHash(txHash);
        transactionService.saveOrUpdateTransaction(assetShareTransactionDO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void joinActivity(ActivityUpsertRequest request, Long activityRewardId) {
        for (ActivityDetailInfo activityDetailInfo : request.getListsObj()) {
            AssetDO joinAsset = null;
            if (activityDetailInfo.getAssetId() != null) {
                joinAsset = assetService.findByShopIdAndAssetId(request.getShopId(), activityDetailInfo.getAssetId());
            } else {
                joinAsset = assetService.findByShopIdAndContractAddress(request.getShopId(),
                        activityDetailInfo.getContractAddress());
            }
            for (Long shareId : activityDetailInfo.getShareIds()) {
                AssetShareDO joinShare = findByAssetIdAndShareId(joinAsset.getId(), shareId);
                joinShare.setActivityRewardId(activityRewardId);
                this.baseMapper.updateById(joinShare);
            }
        }
    }

    @Override
    public void updateActivity(ActivityUpsertRequest request, Long activityRewardId) {
        List<AssetShareDO> lists = findByActivityId(activityRewardId);
        for (AssetShareDO list : lists) {
            list.setActivityRewardId(null);
            this.baseMapper.updateById(list);
        }
        joinActivity(request, activityRewardId);
    }

    @Override
    public List<AssetShareDO> findByActivityId(Long activityRewardId) {
        LambdaQueryWrapper<AssetShareDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AssetShareDO::getActivityRewardId, activityRewardId);
        return this.baseMapper.selectList(queryWrapper);
    }

    @Override
    public void fixTransferTransaction(Integer maxLimit) {
        ArrayList<Integer> statusList = new ArrayList<>();
        statusList.add(TxStatusEnum.PENDING.getStatus());
        ArrayList<Integer> txTypeList = new ArrayList<>();
        txTypeList.add(TxTypeEnum.TRANSFER_SHARE.getTxType());
        txTypeList.add(TxTypeEnum.COLLECTION_SHARE.getTxType());
        txTypeList.add(TxTypeEnum.COMINATION_SHARE.getTxType());
        List<AssetShareTransactionDO> needMintTransaction = transactionService.findShareTransactionByCondition(
                maxLimit, statusList, txTypeList, null, true);
        for (AssetShareTransactionDO assetShareTransactionDO : needMintTransaction) {
            AssetShareDO assetShareDO = this.baseMapper.selectById(assetShareTransactionDO.getAssetShareId());
            TransactionReceipt transactionReceipt = nftService.getTransactionReceiptByTxHash(
                    assetShareTransactionDO.getTxHash());
            //说明没查到数据，可能是因为本地计算了hash，但是没有打包上去
            if (null == transactionReceipt) {
                ChainAccountDO chainAccountDO = accountService.findByShopIdAndAddress(
                        assetShareDO.getShopId(), assetShareDO.getAccount());
                TransactionReceipt txRecpt = nftService.transfer(chainAccountDO.getAddress(),
                        assetShareTransactionDO.getToAccount(),
                        assetShareDO.getShareId(), assetShareTransactionDO.getContractAddress());
                String txHash = txRecpt.getTransactionHash();
                if (null != txHash) {
                    transferPending(txHash, assetShareTransactionDO);
                } else {
                    transferFailed(assetShareDO, assetShareTransactionDO, null);
                }

                // 查询所属资产
                AssetDO assetDO = assetService.getById(assetShareDO.getAssetId());

                // 保存交易记录
                transactionRecordRepo.saveTxRecord(assetDO.getShopId(), txRecpt);
            } else {
                if (!transactionReceipt.isStatusOK()) {
                    transferFailed(assetShareDO, assetShareTransactionDO, assetShareTransactionDO.getTxHash());
                } else {
                    try {
                        BigInteger blockNumber = transactionReceipt.getBlockNumber();
                        BlockInfo block =
                                nftService.getBlockByNumber(blockNumber);
                        long timeStamp = Long.parseLong(block.getFcreateTime());
                        transferSuccess(assetShareDO, assetShareTransactionDO,
                                assetShareTransactionDO.getTxHash(), blockNumber.longValue(),
                                timeStamp);
                    } catch (Exception e) {
                        log.warn("get block error,message:{}", e.getMessage());
                    }
                }
            }
        }
    }

    @Override
    public Integer countAssetShare(TxStatusEnum txStatusEnum) {
        return this.baseMapper.countAssetShare(Optional.ofNullable(txStatusEnum).map(TxStatusEnum::getStatus).orElse(null));
    }

    @Override
    public void fixMintTransaction(Integer maxLimit) {
        ArrayList<Integer> statusList = new ArrayList<>();
        statusList.add(TxStatusEnum.FAILED.getStatus());
        statusList.add(TxStatusEnum.PENDING.getStatus());
        ArrayList<Integer> txTypeList = new ArrayList<>();
        txTypeList.add(TxTypeEnum.GRANT_SHARE.getTxType());
        List<AssetShareTransactionDO> needMintTransaction = transactionService.findShareTransactionByCondition(
                maxLimit, statusList, txTypeList, null, true);
        if (needMintTransaction.isEmpty()) {
            return;
        } else {
            log.info("need to fix mint share,size:{}", needMintTransaction.size());
        }
        for (AssetShareTransactionDO assetShareTransactionDO : needMintTransaction) {
            AssetShareDO assetShareDO = this.baseMapper.selectById(assetShareTransactionDO.getAssetShareId());
            TransactionReceipt transactionReceipt = nftService.getTransactionReceiptByTxHash(
                    assetShareTransactionDO.getTxHash());

            // 查询所属资产
            AssetDO assetDO = assetService.getById(assetShareDO.getAssetId());

            //说明没查到数据，可能是因为本地计算了hash，但是没有打包上去
            if (null == transactionReceipt) {
                TransactionReceipt txRcpt = nftService.mint(assetShareDO.getShareId(),
                        assetShareDO.getAccount(),
                        assetShareTransactionDO.getContractAddress(), assetShareDO.getShareUri());
                String txHash = txRcpt.getTransactionHash();
                mintPending(assetShareDO, txHash, assetShareTransactionDO);

                // 保存交易记录
                transactionRecordRepo.saveTxRecord(assetDO.getShopId(), txRcpt);
            } else {
                if (!transactionReceipt.isStatusOK()) {
                    log.warn("fix mint share,receipt status is not ok,transaction receipt:{}",
                            JSONUtil.toJsonPrettyStr(transactionReceipt));
                    //重新部署
                    TransactionReceipt txRcpt = nftService.mint(assetShareDO.getShareId(),
                            assetShareDO.getAccount(),
                            assetShareTransactionDO.getContractAddress(), assetShareDO.getShareUri());
                    String txHash = txRcpt.getTransactionHash();
                    mintPending(assetShareDO, txHash, assetShareTransactionDO);

                    // 保存交易记录
                    transactionRecordRepo.saveTxRecord(assetDO.getShopId(), txRcpt);
                } else {
                    try {
                        BigInteger blockNumber = transactionReceipt.getBlockNumber();
                        BlockInfo block =
                                nftService.getBlockByNumber(blockNumber);
                        long timeStamp = Long.parseLong(block.getFcreateTime());
                        assetShareService.mintSuccess(assetShareDO, assetShareTransactionDO,
                                assetShareTransactionDO.getTxHash(), blockNumber.longValue(), timeStamp);
                    } catch (Exception e) {
                        log.error("fix mint share,get block error,message:{}", e.getMessage());
                    }
                }
            }
        }
    }

    @Override
    public void fixRecycleTransaction(Integer maxLimit) {
        ArrayList<Integer> statusList = new ArrayList<>();
        statusList.add(TxStatusEnum.FAILED.getStatus());
        statusList.add(TxStatusEnum.PENDING.getStatus());
        ArrayList<Integer> txTypeList = new ArrayList<>();
        txTypeList.add(TxTypeEnum.RECYCLE_SHARE.getTxType());
        List<AssetShareTransactionDO> needMintTransaction = transactionService.findShareTransactionByCondition(
                maxLimit, statusList, txTypeList, null, true);
        if (needMintTransaction.isEmpty()) {
            return;
        }
        log.info("need to fix recycle transaction:{}", needMintTransaction.size());
        for (AssetShareTransactionDO assetShareTransactionDO : needMintTransaction) {
            AssetShareDO assetShareDO = this.baseMapper.selectById(assetShareTransactionDO.getAssetShareId());
            TransactionReceipt transactionReceipt = nftService.getTransactionReceiptByTxHash(
                    assetShareTransactionDO.getTxHash());
            //说明没查到数据，可能是因为本地计算了hash，但是没有打包上去
            if (null == transactionReceipt) {
                ChainAccountDO chainAccountDO = accountService.findByShopIdAndAddress(
                        assetShareDO.getShopId(), assetShareDO.getAccount());
                TransactionReceipt txRecpt = nftService.transfer(chainAccountDO.getAddress(),
                        assetShareTransactionDO.getToAccount(),
                        assetShareDO.getShareId(), assetShareTransactionDO.getContractAddress());
                String txHash = txRecpt.getTransactionHash();
                if (null != txHash) {
                    transferPending(txHash, assetShareTransactionDO);
                } else {
                    transferFailed(assetShareDO, assetShareTransactionDO, null);
                }

                // 查询所属资产
                AssetDO assetDO = assetService.getById(assetShareDO.getAssetId());

                // 保存交易记录
                transactionRecordRepo.saveTxRecord(assetDO.getShopId(), txRecpt);
            } else {
                if (!transactionReceipt.isStatusOK()) {
                    transferFailed(assetShareDO, assetShareTransactionDO, assetShareTransactionDO.getTxHash());
                } else {
                    try {
                        BigInteger blockNumber = transactionReceipt.getBlockNumber();
                        BlockInfo block =
                                nftService.getBlockByNumber(blockNumber);
                        long timeStamp = Long.parseLong(block.getFcreateTime());
                        transferSuccess(assetShareDO, assetShareTransactionDO,
                                assetShareTransactionDO.getTxHash(), blockNumber.longValue(),
                                timeStamp);
                    } catch (Exception e) {
                        log.warn("get block error,message:{}", e.getMessage());
                    }
                }
            }
        }
    }

    @Override
    public void mintShareTransaction(Integer maxLimit) {
        List<AssetMintTaskDO> taskDOList = assetMintTaskService.list(
                new LambdaQueryWrapper<AssetMintTaskDO>()
                        .orderByAsc(AssetMintTaskDO::getUpdatedTime)
                        .last("limit " + maxLimit)
        );
        List<Future<?>> futures = new ArrayList<>(taskDOList.size());

        for (AssetMintTaskDO assetMintTaskDO : taskDOList) {
            // 构建参数
            AssetShareTransactionDO transactionDO = transactionService.getById(assetMintTaskDO.getTxId());

            if (transactionDO == null) {
                log.warn("mint task not found, txId:{}", assetMintTaskDO.getTxId());
                assetMintTaskService.removeById(assetMintTaskDO.getTxId());
                continue;
            }

            AssetDO assetDO = assetService.getById(transactionDO.getAssetId());
            ShareDTO shareDTO = ShareDTO.builder().shareId(transactionDO.getShareId()).
                    toAccount(transactionDO.getToAccount()).assetId(assetDO.getAssetId()).assetPrimaryKey(assetDO.getId())
                    .contractAddress(transactionDO.getContractAddress()).transactionId(transactionDO.getId()).build();

            if (transactionDO.getStatus().equals(TxStatusEnum.SUCCEED.getStatus())) {
                assetMintTaskService.removeById(assetMintTaskDO.getTxId());
                continue;
            }

            // 上区块链
            log.debug("============== 提交授予任务,operation id:{}", transactionDO.getOperationId());
            Future<?> future = threadPoolTaskExecutor.submit(() -> {
                try {
                    mintShare(assetDO, shareDTO);
                    assetMintTaskService.removeById(assetMintTaskDO.getTxId());
                } catch (Exception e) {
                    log.warn("mint share fail, txId: {}", assetMintTaskDO.getTxId(), e);

                    // 推迟下次的执行时间
                    assetMintTaskService.update(
                            new LambdaUpdateWrapper<AssetMintTaskDO>()
                                    .eq(AssetMintTaskDO::getTxId, assetMintTaskDO.getTxId())
                                    .set(AssetMintTaskDO::getUpdatedTime, new Date())
                    );
                }
            });
            futures.add(future);
            log.debug("============== 提交授予任务成功");
        }

        // 等待线程全部完成
        for (Future<?> future : futures) {
            try {
                future.get();
            } catch (InterruptedException | ExecutionException e) {
                log.error("mint share error, {}", e.getMessage());
            }
        }
    }

    @Override
    public void transferShareTransaction(Integer maxLimit) {
        List<AssetTransferTaskDO> taskDOList = assetTransferTaskService.list(
                new LambdaQueryWrapper<AssetTransferTaskDO>()
                        .orderByAsc(AssetTransferTaskDO::getUpdatedTime)
                        .last("limit " + maxLimit)
        );
        List<Future<?>> futures = new ArrayList<>(taskDOList.size());

        for (AssetTransferTaskDO assetTransferTaskDO : taskDOList) {
            // 构建参数
            AssetShareTransactionDO transactionDO = transactionService.getById(assetTransferTaskDO.getTxId());

            if (transactionDO == null) {
                log.warn("transfer task not found, txId:{}", assetTransferTaskDO.getTxId());
                assetTransferTaskService.removeById(assetTransferTaskDO.getTxId());
                continue;
            }

            AssetDO assetDO = assetService.getById(transactionDO.getAssetId());
            ChainAccountDO fromChainAccount = accountService.getById(assetTransferTaskDO.getFromAccountId());
            ShareDTO shareDTO = ShareDTO.builder().shareId(transactionDO.getShareId()).toAccount(transactionDO.getToAccount())
                    .fromAccount(transactionDO.getFromAccount()).assetId(assetDO.getAssetId()).assetPrimaryKey(assetDO.getId())
                    .fromAccountPrivateKey(fromChainAccount.getPrivateKey()).contractAddress(assetDO.getContractAddress())
                    .transactionId(transactionDO.getId()).build();

            if (transactionDO.getStatus().equals(TxStatusEnum.SUCCEED.getStatus())) {
                assetTransferTaskService.removeById(assetTransferTaskDO.getTxId());
                continue;
            }

            // 上区块链
            log.debug("============== 提交授予任务,operation id:{}", transactionDO.getOperationId());
            Future<?> future = threadPoolTaskExecutor.submit(() -> {
                try {
                    transferShare(assetDO, shareDTO);
                    assetTransferTaskService.removeById(assetTransferTaskDO.getTxId());
                } catch (Exception e) {
                    log.warn("transfer share fail, txId: {}", assetTransferTaskDO.getTxId(), e);

                    // 推迟下次的执行时间
                    assetTransferTaskService.update(
                            new LambdaUpdateWrapper<AssetTransferTaskDO>()
                                    .eq(AssetTransferTaskDO::getTxId, assetTransferTaskDO.getTxId())
                                    .set(AssetTransferTaskDO::getUpdatedTime, new Date())
                    );
                }
            });
            futures.add(future);
            log.debug("============== 提交授予任务成功");
        }

        // 等待线程全部完成
        for (Future<?> future : futures) {
            try {
                future.get();
            } catch (InterruptedException | ExecutionException e) {
                log.error("mint share error, {}", e.getMessage());
            }
        }
    }

    private String toHexString(long blockHeight) {
        String blockHeightHex = HexUtil.toHex(blockHeight);
        return addHexPrefix(blockHeightHex);
    }

    private String addHexPrefix(String hexStr) {
        if (hexStr == null) {
            return hexStr;
        }

        if (!hexStr.startsWith("0x") && !hexStr.startsWith("0X")) {
            return "0x" + hexStr;
        }

        return hexStr;
    }
}
